package ma.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;

import ma.model.auxillary.Parameter;
import ma.model.auxillary.ParameterCollection;
import ma.model.auxillary.Parameter.Type;
import ma.model.auxillary.ParameterList;
import ma.model.auxillary.ParameterList.ParameterType;
import ma.view.gui.ObserverInterface;
/**
 * The {@code ManufacturingMethod} class defines a manufacturing method.
 * All manufacturing methods that will be created by a user are inherited by
 * {@code ManfucturingMethod}. The inheritance is followed by <b>DIN 8580</b>.
 * @author Oliver Eckstein
 *
 */
public abstract class ManufacturingMethod
	implements ManufacturingMethodInterface {
    
    protected String name;
    private final int MAX_GEOMETRY_SETS = 32;
    
    /**
     * Number of geometry sets set of this manufacturing method.
     */
    protected int geometrySetsSet = 0;
    
    /**
     * Number of parameters of this manufacturing method.
     */
    protected int parametersCount = 0;   
    
    /**
     * The sub type of this manufacturing based on <b>DIN 8580</b>.
     * I.e.: Drilling, Turning etc.
     */
    protected SubType subType;
    
    /**
     * The super type of this manufacturing based on <b>DIN 8580</b>.
     * I.e.: Machining with geometrically defined edges.
     */
    protected SuperType superType;
    
    /**
     * Whether this manufacturing method was already calculated or not.
     */
    protected boolean calculated = false;
    
    /**
     * Whether an error occurred during the calculation or not.
     */
    protected boolean error = false;

    /**
     * Indicates the length of one geometry set: How many parameters form
     * one geometry set.
     */
    protected int geometrySetLength = 0;
    
    private Collection<ObserverInterface> observers =
            new ArrayList<ObserverInterface>();
    
    
    /**
     * This ArrayList stores all parameters that are set by the user and
     * are used during the whole process. I.e. a drilling process with
     * different holes. Geometry parameters may differ but material or
     * machine dependent parameters are always the same.
     */
    protected List<Parameter> processParameters =
            new ArrayList<Parameter>();
    
    /**
     * This Map stores geometry parameters that are set by the user
     * and all dependent parameters that get computed. It is more dimensional
     * because different geometry sets lead to different dependent parameters.
     */
    protected Map<String, ArrayList<Parameter>>
        mappedParameterList =
        new HashMap<String, ArrayList<Parameter>>();
    
    /**
     * The name of a geometry set
     * (i.e. "Turning shoulder", "Drilling hole" etc.).
     */
    protected String setName;
    
    protected ParameterList parameters = new ParameterList();
    
    /**
     * To create a new instance of {@code ManufacturingMethod},
     * set an identifying name and add parameters
     * (<b>E<sub>min</sub></b>, <b>E</b>).
     * @param name identifying name for this manufacturing method
     */
    public ManufacturingMethod(String name)
    {
        this.name = name;
        createParameters();
        createSuperTypeParameters();
        createSubTypeParameters();
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getName()
     */
    public String getName() {
        return name;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getParameters()
     */
    public ParameterList getParameters()
    {
        return parameters;   
    }
    
    /**
     * To retrieve all geometry and dependent parameters.
     * @return all geometry and dependent parameters.
     */
    public Map<String, ArrayList<Parameter>>
        getGeometryAndDependentParameters() {
        return mappedParameterList;
    }
    
    /**
     * To create the parameters that are valid for every manufacturing method.
     */
    private void createParameters()
    {        
        Parameter E_min = new Parameter(
                "Kumulierter minimaler Energieverbrauch",
                "E_min",
                "<html>E<sub>min</sub></html>",
                "[Wh]",                
                Type.DEPENDENT_PARAMETER,
                ++parametersCount,
                true);
        processParameters.add(E_min);
        
        Parameter E = new Parameter(
                "<html>Gemessener Energieverbrauch<br>"
                        + "(bezogen auf ein Bauteil)</html>",
                "E_ist",
                "<html>E<sub>ist</sub></html>",
                "[Wh]",
                Type.PROCESS_PARAMETER,
                ++parametersCount,
                false);
        processParameters.add(E);
        
        Parameter eta = new Parameter(
                "Prozesswirkungsgrad",
                "eta_pro",
                "<html>&eta;<sub>pro</sub></html>",
                "[1]",
                Type.DEPENDENT_PARAMETER,
                ++parametersCount,
                true);
        processParameters.add(eta);
    }
    
    /**
     * To compute and set the part minimum energy consumption
     * <b>E<sub>min<sub>i</sub></sub></b> [Wh].
     * @param P output <b>P</b> [W] of this manufacturing method     
     * @param t_pro processing time <b>t<sub>pro</sub></b> [s] of whole process
     * (i.e. contains time for engaging)  
     * @return minimum energy consumption
     * <b>E<sub>min<sub>i</sub></sub></b> [Wh]
     */
    protected double setPartMinEnergy(
            double P,            
            double t_pro)
    {
        double E_min_i = P * t_pro / 3600;
        Parameter PE_min_i = new Parameter(
                "Minimaler Energieverbrauch",
                "E_min_i",
                "<html>E<sub>min<sub></sub></sub></html>",
                "[Wh]",                
                Type.DEPENDENT_PARAMETER,
                ++parametersCount,
                true,
                E_min_i
                );
        addParameterToMap(PE_min_i.getAbbreviation(), PE_min_i);
        return E_min_i;
    }
    
    /**
     * To compute and set the minimum energy consumption
     * <b>E<sub>min</sub></b> [Wh]
     * @return minimum energy consumption 
     * <b>E<sub>min<sub>i</sub></sub></b> [Wh]
     */
    public double setMinEnergy()
    {        
        double E_min = 0;
        Collection<Parameter> energyList = getParametersByKey("E_min_i");
        for (Parameter param : energyList)
        {
            E_min += param.getValue();            
        }
        setParameter("E_min", E_min);        
        
        if(error) {
            calculated = false;
        } else {
            calculated = true;
        }
        
        return E_min;
    }
    
    /**
     * To compute and set the effectiveness of this manufacturing method.
     * @return the manufacturing method's effectiveness.
     */
    public double setEffectiveness() {
        double e_act = getParameter("E_ist").getValue();
        double e_min = getParameter("E_min").getValue();
        double eta = e_min / e_act;
        
        if (Double.isInfinite(eta) || Double.isNaN(eta)) {
            error = true;
        }
        
        getParameter("eta_pro").setValue(eta);
        return eta;
    }
    
    /**
     * To get the minimum energy consumption <b>E<sub>min</sub></b> of this
     * manufacturing method.
     * @return the minimum energy consumption <b>E<sub>min</sub></b>.
     * @throws NullPointerException when there is no valid value set for
     * <b>E<sub>min</sub></b>
     */
    public Double getMinEnergy() throws NullPointerException {
        Double eMin = getParameter("E_min").getValue();
        if (eMin == null) {
            throw new NullPointerException(
                    "The manufacturing method '" + this.name
                    +"' was not calculated yet!");
        }
        return eMin;
    }
    
    /**
     * To get the minimum energy consumption <b>E<sub>ist</sub></b> of this
     * manufacturing method.
     * @return the minimum energy consumption <b>E<sub>ist</sub></b>.
     * @throws NullPointerException when there is no valid value set for
     * <b>E<sub>ist</sub></b>
     */
    public Double getActEnergy() throws NullPointerException {
        Double eAct = getParameter("E_ist").getValue();
        if (eAct == null || Double.isNaN(eAct)) {
            throw new NullPointerException(
                    "E_ist of the manufacturing method '" + this.name
                    +"' was not set!");
        }
        return eAct;
    }
    
    /**
     * To add a new Parameter to {@code geometryDependentParameterList}.
     * @param mapKey abbreviation of parameter
     * @param param parameter to be added.
     */
    protected void addParameterToMap(String mapKey, Parameter param)
    {
        ArrayList<Parameter> parameterList =
                mappedParameterList.get(mapKey);
        
        // If list does not exist create it.
        if(parameterList == null) {
            parameterList = new ArrayList<Parameter>();
            parameterList.add(param);
            mappedParameterList.put(mapKey, parameterList);
        } else {
            parameterList.add(param);
        }
    }
    
    /**
     * To set a parameter's value. 
     * @param abbreviation abbreviation of parameter to be set.
     * @param value value of parameter to be set.
     * @throws NoSuchElementException when parameter was not found.     
     */
    /*
     * Ausgelagert!
     */
    public void setParameter(String abbreviation, double value)
            throws NoSuchElementException
    {   
        if(abbreviation != null){
            for (Parameter param: processParameters){
                if(param.getAbbreviation() == abbreviation){
                    param.setValue(value);
                    if(calculated) {
                        calculated = false;
                    }                    
                    return;
                }
            }
        }
        
        
        throw new NoSuchElementException("'" + abbreviation + "' was not found!");
    }
    
    public void setParameterValue(
    		ParameterType parameterType,
    		int index,
    		String parameterAbbreviation,
    		Double value) {
    	
    	parameters.setParameterValue(
    			parameterType, index, parameterAbbreviation, value);
    	
    	if(calculated) {
    		calculated = false;
    	}
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#setParameter(ma.model.Parameter, java.lang.Double)
     */
    public void setParameter(Parameter param, Double value) {
        if(param != null) {
            param.setValue(value);            
        }
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getParameter(java.lang.String)
     */
    public Parameter getParameter(String abbreviation)
            throws NoSuchElementException
    {        
        if(abbreviation != null){
            for (Parameter param: processParameters){
                if(param.getAbbreviation() == abbreviation)
                    return param;            
            }    
        }                
        throw new NoSuchElementException("'" + abbreviation + "' was not found!");
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getParameter(java.lang.String, int)
     */
    public Parameter getParameter(String abbreviation, int index)
    {
        ArrayList<Parameter> parameterList =
                (ArrayList<Parameter>) getParametersByKey(abbreviation);
        return parameterList.get(index);
    }
    
    /**
     * To get all elements of a specific type from
     * {@code geometryDependentParameterList}.
     * @param abbreviation abbreviation of parameter type
     * @return list of elements of type <b>abbreviation</b>
     * @throws NoSuchElementException - when parameter type was not found
     */
    public ArrayList<Parameter> getParametersByKey(String abbreviation)
            throws NoSuchElementException
    {
        ArrayList<Parameter> parameterList =
                mappedParameterList.get(abbreviation);
        
        if(parameterList == null) {
            throw new NoSuchElementException(abbreviation + " not found!"); 
        } else {
            return parameterList;
        }
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getParametersByIndex(int)
     */
    public ArrayList<Parameter> getParametersByIndex(int index)
    {
        ArrayList<Parameter> parameterList = new ArrayList<Parameter>();
        Parameter param;        
        for(String mapKey : mappedParameterList.keySet()){
            if(mappedParameterList.get(mapKey).size() > index) {
                param = getParameter(mapKey, index);
                parameterList.add(param);    
            }            
        }
        return parameterList;
    }
    
    /**
     * To get all parameters of this {@code ManufacturingMethod} of the same
     * category.
     * @param category category of the parameter to be filtered.
     * @return all parameters of the same category.
     */
    public ArrayList<Parameter> getParametersByCategory(
            Parameter.Type category) {
        ArrayList<Parameter> params = new ArrayList<Parameter>();
        
        //First, all parameters of materialMachineParameterList
        for(Parameter param : processParameters) {
            if(param.getCategory() == category) {
                params.add(param);
            }
        }
        
        //Second, all parameters of geometryDependentParameterList 
        for(ArrayList<Parameter> param :
            mappedParameterList.values()) {
            if(param != null) {
                if(param.get(0).getCategory() == category) {
                    params.addAll(param);
                }
            }
        }
        return params;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getUserSetParametersCount()
     */
    public int getUserSetParametersCount() {
        int count = 0;
        Parameter.Type cat;        
        //First, all parameters of materialMachineParameterList
        for(Parameter param : processParameters) {
        
            cat = param.getCategory();
            if(!param.getDependence()) {
                if(cat == Parameter.Type.MATERIAL_PARAMETER ||
                        cat == Parameter.Type.PROCESS_PARAMETER) {
                    count++;
                }    
            }                    
        }
        
      //Second, all parameters of geometryDependentParameterList 
        for(ArrayList<Parameter> param :
            mappedParameterList.values()) {
            if(param != null && !param.get(0).getDependence()) {
                cat = param.get(0).getCategory();
                if(cat == Parameter.Type.GEOMETRY_PARAMETER ||            
                        cat == Parameter.Type.MATERIAL_PARAMETER ||
                        cat == Parameter.Type.PROCESS_PARAMETER) {
                    count += param.size();
                }
            }
        }        
        return count;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getGeometryCount()
     */
    public int getSetCount()
    {
        return geometrySetsSet;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getMaxGeometrySets()
     */
    public int getMaxGeometrySets()
    {
        return MAX_GEOMETRY_SETS;
    }
    
    /**
     * To add a geometry set to this process.
     * @param parameterList array that contains all parameters to be added
     * @throws IndexOutOfBoundsException when too much parameters were set
     */
    protected void addGeometrySet(ArrayList<Parameter> parameterList)
            throws IndexOutOfBoundsException
    {
        if (geometrySetsSet == MAX_GEOMETRY_SETS)
        {
            throw new IndexOutOfBoundsException(
                    "Maximum number of geometry sets reached!");
        }
        
        for(Parameter param : parameterList)
        {
            addParameterToMap(param.getAbbreviation(), param);            
        }
        geometrySetsSet++;        
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#removeGeometrySet()
     */
    public void removeGeometrySet() throws IndexOutOfBoundsException {        
        if (geometrySetsSet == 1) {
            throw new IndexOutOfBoundsException(
                    "Minimum number of geometry sets reached!");
        }
        
        
        for(Entry<String, ArrayList<Parameter>> entry :
            mappedParameterList.entrySet()) {
            String mapKey = entry.getKey();
            ArrayList<Parameter> params = entry.getValue();
            if(params.size() == geometrySetsSet) {
                params.remove(geometrySetsSet - 1);
                mappedParameterList.put(mapKey, params);
            }
        }
        geometrySetsSet--;        
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#addGeometrySet()
     */
    public void addGeometrySet() {
        
        ArrayList<Parameter> params = getParametersByIndex(0);
        ArrayList<Parameter> newParams = new ArrayList<Parameter>();
        
        for(Parameter param : params) {
            if(param.getCategory() == Parameter.Type.GEOMETRY_PARAMETER) {
                Parameter newParam = new Parameter(
                        param.getName(),
                        param.getAbbreviation(),
                        param.getGuiAbbreviation(),
                        param.getUnit(),
                        param.getCategory(),
                        param.getOrder(),
                        param.getDependence()
                        );
                newParams.add(newParam);
            }
        }
        addGeometrySet(newParams);        
    }
    
    /**
     * Before every calculation all non geometric parameters get deleted to
     * make sure that no parameters from former calculations rest in this
     * manufacturing method.
     */
    protected void clearParameterTable() {
        for(Iterator<Map.Entry<String, ArrayList<Parameter>>> i =
                mappedParameterList.entrySet().iterator();
                i.hasNext();) {
            Map.Entry<String, ArrayList<Parameter>> entry = i.next();
            if (entry.getValue().get(0).getCategory() !=
                    Parameter.Type.GEOMETRY_PARAMETER) {                
                i.remove();
            }
        }        
    }
    
    /**
     * To get the length of one geometry set of this manufacturing method.
     * @return length of one geometry set.
     */
    public int getGeometrySetLength() {
        return geometrySetLength;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.Observable#registerObserver(ma.view.gui.ObserverInterface)
     */
    public void registerObserver(ObserverInterface observer)
    {
        observers.add(observer);
    }
    
    @Override
    /* (non-Javadoc)
     * @see ma.model.Observable#deleteObserver(ma.model.Observable)
     */
    public void removeObserver(ObserverInterface observer)
    {
        if(observers.contains(observer))
        {
            observers.remove(observer);            
        }
    }
    
    @Override
    /* (non-Javadoc)
     * @see ma.model.Observable#notifyObservers(ma.model.Observable)
     */
    public void notifyObservers()
    {   
        for(ObserverInterface observer : observers)
        {
            observer.update();                    
        }        
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#setName(java.lang.String)
     */
    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getType()
     */
    public SubType getSubType() {
        return subType;
    }
    
    @Override
    public String toString() {
        return(subType + "." + name);
    }
    
    /**
     * To get all dependent parameters of this manufacturing method.
     * @return all dependent parameters of this manufacturing method.
     */
    private List<Parameter> getDependentParameters() {
        List<Parameter> result = new ArrayList<Parameter>();
        for(Parameter param : processParameters) {
            if(!param.getDependence()) {
                result.add(param);
            }                    
        }        
       
        for(ArrayList<Parameter> params :
            mappedParameterList.values()) {
            if(params != null && !params.get(0).getDependence()) {
                result.addAll(params);
                }
            }
        return result;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#allSet()
     */
    public boolean allSet() {
        for(Parameter param : getDependentParameters()) {
            if(!param.isSet()) {
                return false;
            }
        }        
        return true;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ResultModelInterface#isCalculated()
     */
    public boolean isCalculated() {
        return calculated;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getShortResults()
     */
    public List<Parameter> getShortResults() {
        List<Parameter> result = new ArrayList<Parameter>();
        result.add(getParameter("E_min"));
        result.add(getParameter("E_ist"));
        result.add(getParameter("eta_pro"));
        return result;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getSuperType()
     */
    public SuperType getSuperType() {
        return superType;
    }
    
    /**
     * To create the parameters that specifically define this manufacturing
     * method.
     */
    protected abstract void createSubTypeParameters();
    
    /**
     * To create parameters that define the super type of this manufacturing
     * method.
     */
    protected abstract void createSuperTypeParameters();
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ResultModelInterface#getMainResults()
     */
    public List<Parameter> getMainResults() {
    	List<Parameter> resultSet = new ArrayList<Parameter>();        
        Collections.sort(processParameters);
        for(Parameter param : processParameters) {            
            if(param.getCategory() == Parameter.Type.DEPENDENT_PARAMETER) {
            	resultSet.add(param);            	                
            }
        }
        return resultSet;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ResultModelInterface#getSetResults(int)
     */
    public List<Parameter> getSetResults(int setIndex) 
    		throws IllegalArgumentException {
    	if(setIndex < 0 || setIndex >= geometrySetsSet) {
    		throw new IllegalArgumentException("Invalid setIndex: " + setIndex 
					+ ". Size: " + geometrySetsSet);
    	}
    	
    	List<Parameter> resultSet = new ArrayList<Parameter>();
    	for(Parameter param : mappedParameterList.get(setIndex)) {                
            if (param.getCategory() == Parameter.Type.DEPENDENT_PARAMETER) {
                resultSet.add(param); 
            }
        }
    	return resultSet;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ResultModelInterface#getSetName(int)
     */
    public String getSetName(int setIndex) throws IllegalArgumentException {
    	if(setIndex < 0 || setIndex >= geometrySetsSet) {
    		throw new IllegalArgumentException("Invalid setIndex: " + setIndex 
					+ ". Size: " + geometrySetsSet);
    	}
    	return (setIndex + 1) + ". " + setName;
    }
    
    @Override
    /*
     * (non-Javadoc)
     * @see ma.model.ManufacturingMethodInterface#getSetName()
     */
    public String getSetName() {
    	return setName;
    }
}
